Skip to content

fix(ios): correct ratioOverlay aspect calculation and orientation - #818

Open
ZayanKhan-12 wants to merge 1 commit into
teslamotors:masterfrom
ZayanKhan-12:fix/195-ratio-overlay-aspect
Open

ZayanKhan-12 wants to merge 1 commit into
teslamotors:masterfrom
ZayanKhan-12:fix/195-ratio-overlay-aspect

Conversation

@ZayanKhan-12

@ZayanKhan-12 ZayanKhan-12 commented Sep 19, 2026 •

Copy link
Copy Markdown

Fixes #195

The bug

ratioOverlay renders a window that is both inverted and the wrong shape for every ratio except 1:1 — exactly as reported in #195, where '4:5' drew vertical bars and '5:4' drew a window that was neither 4:5 nor 5:4.

There are two independent defects in RatioOverlayView.swift.

1. The ratio string is parsed backwards. RatioOverlayData.init(from:) read values[0] into height and values[1] into width:

let inputHeight = Float(values[0]),   // "4"
let inputWidth  = Float(values[1]),   // "5"

So '4:5' was handled as a 5:4 window. The README documents the prop with '16:9', i.e. width:height, and that is what callers expect.

2. The geometry derives the window from the wrong axis. The centre window was sized as frame.height * ratio (or frame.width / ratio) instead of from the axis actually being held fixed. The output therefore depended on the preview's aspect ratio rather than only on the requested one, so the window never matched the ratio asked for. 1:1 sat on its own hard-coded branch, which is why 1:1 was the only case users saw working.

Measured on a 375×812 preview, master produces:

input window actual ratio bars expected
1:1 375×375 1.000 top/bottom 1.000 ✅
4:5 300×812 0.369 left/right 0.800 ❌
5:4 375×650 0.577 top/bottom 1.250 ❌
16:9 375×457 0.821 top/bottom 1.778 ❌
9:16 211×812 0.260 left/right 0.562 ❌
4:3 375×609 0.616 top/bottom 1.333 ❌

The fix

Parse width:height, then compute the window as a plain aspect fit: the largest rect of the requested ratio that fits inside the preview, centred, with the leftover strips masked.

let windowSize = targetRatio > containerRatio
    ? CGSize(width: size.width, height: size.width / targetRatio)
    : CGSize(width: size.height * targetRatio, height: size.height)

This collapses the three near-duplicate branches into one, and 1:1 now falls out of the general case instead of needing its own branch. The swiftlint:disable function_body_length suppression is no longer needed and has been removed.

It also guards against zero/negative components ('4:0', '-4:5') and a zero-sized preview, which previously produced NaN or an inverted frame rather than simply hiding the overlay.

Verification

The layout math was extracted from the shipped file and exercised directly — 343 assertions, all passing:

  • parsing is width:height, and 12 malformed inputs ('4:0', '-4:5', '4:5:6', 'x:y', …) hide the overlay instead of drawing garbage;
  • 4 degenerate preview sizes hide the overlay;
  • across 12 ratios × 5 preview sizes (iPhone portrait/landscape, square, a small custom style, iPad), the window always (a) has the requested ratio, (b) fits inside the preview, (c) touches at least one edge — i.e. it is maximal, and (d) is centred.

The real RatioOverlayView was then laid out by UIKit, rendered to a bitmap, and the visible window measured back out of the pixels:

ratio | measured window (px) | measured ratio | expected | verdict
------|----------------------|----------------|----------|--------
1:1   |    0, 219  375x374   |          1.003 |    1.000 | PASS
4:5   |    0, 172  375x468   |          0.801 |    0.800 | PASS
5:4   |    0, 256  375x300   |          1.250 |    1.250 | PASS
16:9  |    0, 301  375x210   |          1.786 |    1.778 | PASS
9:16  |    0,  73  375x666   |          0.563 |    0.562 | PASS
4:3   |    0, 266  375x280   |          1.339 |    1.333 | PASS
1:4   |   86,   0  203x812   |          0.250 |    0.250 | PASS

'4:5' now draws a window taller than it is wide with bars top and bottom — the behaviour the issue asks for.

Also run locally: swiftlint on the changed file (0 violations), yarn build, yarn lint, and a
ReactNativeCameraKit pod-target build for iphoneos/arm64 against the example app's CocoaPods
setup with Fabric codegen — ** BUILD SUCCEEDED **. (The full CameraKitExample scheme does not
build on Xcode 26: the vendored fmt pod fails with call to consteval function ... is not a constant expression, a React Native dependency issue unrelated to this change.)

Compatibility

This changes observable behaviour for anyone who compensated for the bug by passing an inverted string (e.g. '5:4' to get a 4:5 window). That is unavoidable if the prop is to match its documentation — the README has been updated to state the width:height convention explicitly. Worth calling out in release notes.

`ratioOverlay` produced a window that was both inverted and the wrong
shape for every ratio except `1:1`.

Two separate defects:

1. Parsing was reversed. `RatioOverlayData` read `values[0]` into
   `height` and `values[1]` into `width`, so `'4:5'` was treated as a
   5:4 window. The README documents the prop as `'16:9'`, i.e.
   width:height, and that is what callers expect.

2. The geometry was wrong. The centre window was derived from the
   opposite axis of the preview: `centerSize.height` was
   `frame.height * ratio` (and `centerSize.width` was
   `frame.width / ratio`) instead of being derived from the axis being
   held fixed. The result depended on the preview's own aspect ratio, so
   the window never actually matched the requested ratio. `1:1` was
   hard-coded on a separate branch, which is why only `1:1` looked right.

On a 375x812 preview, master produced:

    input   window      actual ratio   bars
    1:1     375x375     1.000          top/bottom   ok
    4:5     300x812     0.369          left/right   wrong
    5:4     375x650     0.577          top/bottom   wrong
    16:9    375x457     0.821          top/bottom   wrong

The ratio window is now computed as a plain aspect fit: the largest rect
of the requested ratio that fits the preview, centred, with the leftover
strips masked. That collapses the three near-duplicate branches into one
and makes `1:1` fall out of the general case rather than being special.

Also guards against zero/negative components and zero-sized previews,
which previously produced a NaN or an inverted frame instead of simply
hiding the overlay.

Fixes teslamotors#195
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ratio Overlay calculation display issue

2 participants